package cn.lg.soar.system.biz.modules.auth.service.impl;

import cn.lg.soar.common.data.tree.ITreeResult;
import cn.lg.soar.common.util.AssertUtil;
import cn.lg.soar.common.util.current.CurrentProxy;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.data.TreeUtil;
import cn.lg.soar.core.annotations.SoarCache;
import cn.lg.soar.core.annotations.SoarCacheEvict;
import cn.lg.soar.database.base.service.impl.TreeServiceImpl;
import cn.lg.soar.system.api.enums.MenuStatusEnum;
import cn.lg.soar.system.api.enums.MenuTypeEnum;
import cn.lg.soar.system.biz.config.SystemCacheKey;
import cn.lg.soar.system.biz.modules.auth.entity.Menu;
import cn.lg.soar.system.biz.modules.auth.entity.MenuPermitRel;
import cn.lg.soar.system.biz.modules.auth.entity.Permit;
import cn.lg.soar.system.biz.modules.auth.entity.RoleMenuRel;
import cn.lg.soar.system.biz.modules.auth.mapper.MenuMapper;
import cn.lg.soar.system.biz.modules.auth.model.MenuBatchCreateVO;
import cn.lg.soar.system.biz.modules.auth.model.MenuTree;
import cn.lg.soar.system.biz.modules.auth.model.MenuVo;
import cn.lg.soar.system.biz.modules.auth.service.IMenuService;
import cn.lg.soar.system.biz.modules.auth.service.IPermitService;
import cn.lg.soar.system.biz.modules.auth.service.MenuPermitRelService;
import cn.lg.soar.system.biz.modules.auth.service.RoleMenuRelService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author luguoxiang
 * @date 2021/8/15
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class MenuServiceImpl extends TreeServiceImpl<Integer, MenuMapper, Menu> implements IMenuService, CurrentProxy<IMenuService> {

    @Autowired
    private RoleMenuRelService roleMenuRelService;
    @Autowired
    private MenuPermitRelService menuPermitRelService;
    @Autowired
    private IPermitService permitService;

    @Caching(evict = {
            @CacheEvict(value = { SystemCacheKey.ROLEID_TO_MENUIDS}, allEntries = true),
    })
    @SoarCacheEvict(value = SystemCacheKey.TREE, key = "'menu'")
    @Override
    public void clearCache() {}

    @SoarCacheEvict(value = SystemCacheKey.TREE, key = "'menu'")
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void create(MenuVo vo) {
        Menu menu = vo.toMenu();
        AssertUtil.isTrue(save(menu), "创建失败");
        menuPermitRelService.setMenuPermit(menu.getId(), vo.getPermitIds());
        // 添加菜单权限数据
        if (MenuTypeEnum.menu.eq(vo.getType())) {
            List<MenuVo> permits = vo.getPermits();
            for (MenuVo permit : permits) {
                permit.setParentId(menu.getId());
                permit.setType(MenuTypeEnum.button.getValue());
                if (permit.getStatus() == null) {
                    permit.setStatus(MenuStatusEnum.normal.getValue());
                }
                AssertUtil.isTrue(save(permit), "添加权限失败");
                menuPermitRelService.setMenuPermit(permit.getId(), permit.getPermitIds());
            }
        }
    }

    @SoarCacheEvict(value = SystemCacheKey.TREE, key = "'menu'")
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void update(MenuVo vo) {
        Menu menu = vo.toMenu();
        AssertUtil.isTrue(updateById(menu), "更新失败");
        menuPermitRelService.setMenuPermit(menu.getId(), vo.getPermitIds());
        // 添加菜单权限数据
        if (MenuTypeEnum.menu.eq(vo.getType())) {
            List<MenuVo> permits = vo.getPermits();
            // 删除多余的权限
            List<Integer> ids = permits.stream().map(Menu::getId).filter(Objects::nonNull).collect(Collectors.toList());
            remove(
                    Wrappers.<Menu>lambdaQuery()
                            .eq(Menu::getParentId, menu.getId())
                            .notIn(DataUtil.isValuable(ids), Menu::getId, ids)
            );
            // 添加新的权限
            for (MenuVo permit : permits) {
                permit.setParentId(menu.getId());
                permit.setType(MenuTypeEnum.button.getValue());
                if (permit.getStatus() == null) {
                    permit.setStatus(MenuStatusEnum.normal.getValue());
                }
                AssertUtil.isTrue(saveOrUpdate(permit), "添加权限失败");
                menuPermitRelService.setMenuPermit(permit.getId(), permit.getPermitIds());
            }
        }
    }

    @SoarCacheEvict(value = SystemCacheKey.TREE, key = "'menu'")
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public boolean removeByIds(Collection<?> list) {
        if (DataUtil.isEmpty(list)) {
            return true;
        }
        Set<?> set = new HashSet<>(list);
        List<MenuTree> tree = proxy().tree();
        Set<Object> ids = new HashSet<>(list);
        ITreeResult.forEach(tree, x -> {
            if (set.contains(x.getId())) {
                ids.add(x.getId());
                ITreeResult.forEach(x.getChildren(), y -> ids.add(y.getId()));
            }
        });
        roleMenuRelService.remove(Wrappers.<RoleMenuRel>lambdaQuery().in(RoleMenuRel::getMenuId, ids));
        menuPermitRelService.remove(Wrappers.<MenuPermitRel>lambdaQuery().in(MenuPermitRel::getMenuId, ids));
        return super.remove(Wrappers.<Menu>lambdaQuery().in(Menu::getId, ids));
    }

    @Override
    public MenuVo detail(Integer id) {
        Menu menu = getById(id);
        if (menu == null){
            return null;
        }
        MenuVo vo = new MenuVo(menu);
        // 接口权限
        List<Integer> permitIds = menuPermitRelService.getPermitIds(id);
        vo.setPermitIds(permitIds);
        // 按钮列表
        if (MenuTypeEnum.menu.eq(menu.getType())) {
            List<MenuVo> children = getChildren(id)
                    .stream()
                    .map(MenuVo::new)
                    .collect(Collectors.toList());
            for (MenuVo menuVo : children) {
                menuVo.setPermitIds(menuPermitRelService.getPermitIds(menuVo.getId()));
            }
            vo.setPermits(children);
        }
        return vo;
    }

    @SoarCache(value = SystemCacheKey.TREE, key = "'menu'")
    @Override
    public List<MenuTree> tree() {
        List<Menu> menus = list(
                Wrappers.<Menu>lambdaQuery()
                        .select(
                                Menu::getId,
                                Menu::getCode,
                                Menu::getPath,
                                Menu::getRedirect,
                                Menu::getComponent,
                                Menu::getTitle,
                                Menu::getIcon,
                                Menu::getHideChildren,
                                Menu::getKeepalive,
                                Menu::getShared,
                                Menu::getParentId,
                                Menu::getType,
                                Menu::getStatus,
                                Menu::getSort
                        )
                        .in(Menu::getStatus, MenuStatusEnum.normal.getValue(), MenuStatusEnum.hidden.getValue())
                        .orderByAsc(Menu::getParentId, Menu::getSort)
        );
        return ITreeResult.valueOf(menus, 0, MenuTree::new);
    }

    @Caching(evict = {
            @CacheEvict(value = SystemCacheKey.MENUID_TO_PERMITS, allEntries = true),
            @CacheEvict(value = SystemCacheKey.ROLEID_TO_PERMITS, allEntries = true),
    })
    @SoarCacheEvict(value = SystemCacheKey.TREE, key = "'menu'")
    @Override
    public void batchCreate(List<MenuBatchCreateVO> tree) {
        Set<String> codes = new HashSet<>();
        TreeUtil.forEach(tree, x -> {
            AssertUtil.isFalse(codes.contains(x.getCode()), "重复code，{0}", x.getCode());
            codes.add(x.getCode());
        });
        Map<String, Menu> map = list(Wrappers.<Menu>lambdaQuery().in(Menu::getCode, codes))
                .stream()
                .collect(Collectors.toMap(Menu::getCode, x -> x));
        // 查接口权限
        permitService.scanServletPathGenPermit();
        List<Permit> permits = permitService.list(Wrappers.<Permit>lambdaQuery().select(Permit::getCode, Permit::getId));
        Map<String, Integer> permitMap = permits.stream().collect(Collectors.toMap(Permit::getCode, Permit::getId));
        for (MenuBatchCreateVO vo : tree) {
            vo.setParentId(0);
        }
        // 菜单权限
        Set<MenuPermitRel> dbMenuPermits = new HashSet<>(menuPermitRelService.list(
                Wrappers.<MenuPermitRel>lambdaQuery().select(MenuPermitRel::getMenuId, MenuPermitRel::getPermitId)
        ));

        Params params = new Params();
        params.menuMap = map;
        params.permitMap = permitMap;
        params.dbMenuPermits = dbMenuPermits;
        params.menuPermits = new HashSet<>(codes.size());
        handleMenu(tree, params);
        menuPermitRelService.saveBatch(params.menuPermits);
    }

    private void handleMenu(List<MenuBatchCreateVO> list, Params params) {
        List<Menu> menus = new ArrayList<>(list.size());
        List<Menu> insets = new ArrayList<>(list.size());
        List<Menu> updates = new ArrayList<>(list.size());
        for (MenuBatchCreateVO vo : list) {
            Menu menu = params.menuMap.get(vo.getCode());
            if (menu == null) {
                menu = new Menu();
                BeanUtils.copyProperties(vo, menu);
                insets.add(menu);
                menus.add(menu);
                continue;
            }
            menus.add(menu);
            menu.setComponent(vo.getComponent());
            updates.add(menu);
        }
        saveBatch(insets);
        updateBatchById(updates);
        // 处理权限和下级
        List<MenuBatchCreateVO> cs = new ArrayList<>(list.size() << 3);
        for (int i = 0; i < list.size(); i++) {
            Menu menu = menus.get(i);
            MenuBatchCreateVO vo = list.get(i);
            Integer id = menu.getId();
            String[] permits = vo.getPermits();
            if (permits != null) {
                for (String permit : permits) {
                    Integer permitId = params.permitMap.get(permit);
                    if (permitId != null) {
                        MenuPermitRel menuPermit = new MenuPermitRel();
                        menuPermit.setMenuId(id);
                        menuPermit.setPermitId(permitId);
                        if (!params.dbMenuPermits.contains(menuPermit)) {
                            params.menuPermits.add(menuPermit);
                        }
                    }
                }
            }
            // 下一层
            List<MenuBatchCreateVO> children = vo.getChildren();
            if (children != null) {
                for (MenuBatchCreateVO child : children) {
                    child.setParentId(id);
                    cs.add(child);
                }
            }
        }
        if (!cs.isEmpty()) {
            handleMenu(cs, params);
        }
    }

    private class Params {
        Map<String, Menu> menuMap;
        Map<String, Integer> permitMap;
        Set<MenuPermitRel> dbMenuPermits;
        Set<MenuPermitRel> menuPermits;
    }

}


